Об`єктно-орієнтований підхід до програмування

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Об'єкт можна порівнювати з чорним ящиком. Фокусник кладе в нього хустинку, говорить заповітне заклинання, і витягує кролика. Так само і ми. Ми можемо ініціалізувати об'єкт, або він сам ініціалізується значеннями за замовчуванням, викликати потрібний метод об'єкта, і отримати результат. Нас мало цікавить те, що в ньому конкретно відбувається, якщо об'єкт вже досить добре налагоджений. Основна ідея об'єктно-орієнтованого підходу полягає в наявності інтерфейсу, який служить для поліморфного поводження з об'єктом і його нащадками. За рахунок наявності інтерфейсу легко досягається повторне використання коду. Багато програмістів, що переходять від процедурного програмування до об'єктно-орієнтованого програмування справедливо зауважують, що вони можуть зробити все те ж саме і без використання об'єктів. Об'єктно-орієнтоване програмування - це всього лише угода про правила побудови програм. Вся міць об'єктної орієнтації розкривається у великих проектах, або при написанні великої кількості однотипних програм, наприклад програм, що працюють з базами даних. За рахунок повторного використання коду досягається простота в роботі програміста (накопичення досвіду), скорочується розмір програми (методи об'єктів одного типу або методи, успадковані від предків нащадками існують в єдиному екземплярі), самодокументований, а значить і більше простоти при налагодженні (об'єкти описуються в певному місці програми окремо від реалізації), простота супроводу програми (не міняючи інтерфейс об'єкту, Ви можете змінити реалізацію методів) і т.д. Але, це тільки в ідеалі. Насправді достатньо просто перекрутити постулати об'єктної орієнтованості. Все залежить від правильності і лаконічності дерева успадкування Вашої бібліотеки об'єктів. Нам пощастило, Ми можемо використовувати у своїй роботі останнє досягнення в області об'єктно-орієнтованого програмування - продукт компанії Borland-Inprise Delphi.Прі подальшому читанні тексту, якщо Вам буде відразу щось незрозуміло, то продовжуйте читати далі. У такій складній темі важко послідовно викласти все по порядку, тому що багато питань переплітаються з більш складними і навпаки. По ходу читання тексту, Ви складете повне уявлення про тему.

Об'єктно-орієнтоване програмування.

Об'єкт в Delphi представляє з себе спеціальну структуру, яка описує поля, властивості і методи об'єкта - class. Предком для всіх об'єктів служить class Tobject. Давайте розглянемо простий об'єкт.

Type

TmyObject = class (TObject)

Private / / закрита частина

AmyField: Integer; / / Властивість

Protected / / Захищена частина

Procedure SetMyField (Val: Integer); / / Процедура запису властивості класу

Public / / Відкрита частина

Constructor Create; / / Конструктор

Destructor Destroy; override; / / Деструктор

Property MyField: Integer read AmyField write SetMyField; / / Властивість класу

End;

Імена класів прийнято починати з літери T, але це просто угоду, а не правило. Ви можете назвати Ваш об'єкт як хочете. Однак, буква Т на початку імені класу - це правило хорошого тону. Далі, вказується, що цей клас є нащадком від Tobject. Якщо Ви запишіть TmyClass = class, то все одно ваш клас буде нащадком від Tobject. Далі, йде закрита частина інтерфейсу класу. Тут оголошуються властивості і методи класу, які будуть доступними тільки з методів цього ж класу, і будуть недоступними для інших класових методів і з інших модулів програми. При спадкуванні класу, нащадок теж не буде мати доступу до закритої частини інтерфейсу. Іноді, така поведінка класу незручно. Наприклад, при великій кількості звернень до списку даних одного класу з іншого через відкриту частину інтерфейсу, при кожному зверненні, можливо, будуть перевірятися допустимі межі індексу списку. Це правильно, але може значно сповільнити роботу програми, тому було б непогано мати можливість для обмеженого числа класів або функцій дозволити доступ до закритої частини, щоб вони могли звертатися до властивостей класу, оголошеним в закритій частині. Можливо, Ви писали на С + + і знаєте, що там такі класи та функції називаються друзями. У Delphi ця можливість реалізується через оголошення дружніх класів та функцій в одному модулі програми, тобто всі друзі повинні бути оголошені в одному модулі. Далі, йде захищена частина. Вона відрізняється від закритої тим, що з нащадка класу, Ви можете мати доступ до цієї частини. Далі, йде відкрита частина інтерфейсу. Тут Ви можете оголосити властивості і методи класу, які будуть доступні для інших класів, процедур і функцій. Є ще одна частина інтерфейсу - published (опублікована). Ця частина має місце у нащадків від Tcomponent. Delphi використовує цю частину інтерфейсу в інспекторі об'єктів. При доступі до класу під час виконання програми, ця частина нічим не відрізняється від public. Тут має сенс оголошувати властивості та події класу. Всі властивості і події будуть доступні з інспектора об'єктів, і Ви зможете редагувати їх під час розробки. Щоб працювати з класом, Ви повинні оголосити змінну об'єктного типу цього класу, потім ініціалізувати її викликом конструктора.

Type

TmyClass = class (TObject) / / Оголошення класу

...

end;

...

Var

AmyClass: TmyClass; / / Оголошення змінної класу

begin

AmyClass: = TmyClass.Create; / / Виклик конструктора, зверніть увагу на те, що викликається конструктор / / TmyClass.Create, а не AmyClass.Create

Try

...

finally

AmyClass.Free; / / Знищення класу

End;

end;

Класи в Delphi можуть створюватися тільки у динамічній пам'яті, тому всі змінні об'єктного типу - це покажчики на екземпляр класу в динамічній пам'яті. Типове ім'я конструктора - Create, типове ім'я деструктора - Destroy. Дотримуйтеся цього правила, якщо немає особливої ​​необхідності в зворотному.

Ініціалізація і руйнування об'єктів

Для оголошення конструктора використовується зарезервоване слово constructor, після якого йде ім'я конструктора і параметри, якщо необхідно. Конструктор повертає покажчик на екземпляр класу. У конструктора Tobject ім'я Create, тому у всіх нащадків цього класу є конструктор Create, хоча, у деяких класів є й інші конструктори з іншими іменами, наприклад у обробників виключень. У тілі конструктора Ви можете викликати конструктор предка для ініціалізації закритої частини предка значеннями за замовчуванням, наприклад:

unit MyUnit;

interface

Type

TmyClass = class (TComponent)

...

public

constructor Create (AOwner: TComponent); override; / / перевантажуємо конструктор предка

...

end;

implementation

Constructor TmyClass.Create (AOwner: TComponent);

Begin

Inherited Create (Aowner); / / Виклик конструктора предка

... / / Подальша ініціалізація об'єкта

End;

Якщо ім'я конструктора предка збігається з ім'ям нащадка, то можна скоротити запис при виклику конструктора предка в конструкторі нащадка:

Constructor TmyClass.Create (AOwner: TComponent);

Begin

Inherited (Aowner); / / Виклик конструктора предка

... / / Подальша ініціалізація об'єкта

End;

Для знищення об'єкта служить деструктор. Деструктор оголошується за допомогою зарезервованого слова destructor, після якого йде ім'я деструкції. Деструктор нічого не повертає і не має параметрів. Я раджу Вам замість прямого виклику деструктора використовувати метод Free. Цей метод є у всіх класів у Delphi, тому що успадковується від Tobject. Цей метод спочатку перевіряє нерівність покажчика на клас nil, а потім тільки викликає Destroy. Це більш безпечний спосіб знищити об'ект.unit MyUnit;

interface

Type

TmyClass = class (TComponent)

...

public

constructor Create (AOwner: TComponent); override; / / перевантажуємо конструктор предка

destructor Destroy; override / / Перенавантажуємо деструктор предка

...

end;

implementation

...

destructor TmyClass.Destroy;

Begin

... / / Знищення об'єкта

Inherited Destroy; / / Виклик деструктора предка, для знищення закритих полів предка

End;

Інкапсуляція

Суть об'єктно-орієнтованого підходу до програмування полягає в трьох принципах: інкапсуляції, успадкування та поліморфізм. Для більш надійної роботи програми, Ви не повинні безпосередньо звертатися до полів об'єкта. Замість цього, Ви повинні звертатися через спеціальні методи, які можуть перевірити, наприклад, на допустимість значення в заданому інтервалі або одночасно виконати додаткову роботу по зміні стану об'єкта, в залежності від зміни значення поля - це і є інкапсуляція. Така можливість реалізується в класах через оголошення властивостей об'єкта. Властивість дозволяє обмежити права на зміну значення поля та організовує доступ до поля через методи. Ось приклад опису класу з властивістю:

Type

TmyClass = class (TObject)

Private

AmyField: Integer; / / Оголошення поля цілого типу

Protected

Procedure SetMyField (Val: Integer); virtual; / / Оголошення процедури для запису значення властивості

Public

Property MyField: Integer read AmyField write SetMyField; / / оголошення властивості

End;

Тут ми бачимо, що властивість MyField є цілим типом. Воно доступне для читання і запису, тому що оголошені методи read і write. Процедура, організуюча запис значення оголошена в секції protected для того, щоб у разі необхідності, Ви могли перевантажити її в нащадку. Насправді значення властивості зберігається в полі AmyField класу. Якщо Ви не оголосите методу write, то властивість стане доступним тільки з читання, аналогічно і з методом read. До слова кажучи, поля для зберігання значення властивості може і не бути, головне, щоб були оголошені методи доступу до нього. Для прикладу, клас, який реалізує інтерфейс доступу до властивостей дисплея, міг би мати властивості ширина і висота в пікселях. Вам не обов'язково зберігати ці значення, тому що можна викликати стандартну функцію Windows і дізнатися ширину і висоту екрану, тим більше, що в процесі роботи ці значення можуть мінятися при перемиканні в інший режим. Методи запису і читання властивості підпорядковуються жорсткими правилами. Так для читання властивості, Вам необхідно оголосити функцію без формальних параметрів, що повертає значення того ж типу, що і властивість. Для запису значення, Вам необхідно оголосити процедуру з одним параметром того ж типу, що і властивість. Якщо в якості методу для запису або читання властивості Ви вказуєте ім'я поля для зберігання значення властивості, то це аналогічно прямому доступу до цього поля. При компіляції такого способу звернення до властивості, код буде оптимізовано, тому це не спричинить ніяких додаткових витрат ресурсів комп'ютера. Зазвичай, такий метод звернення до поля застосовують для читання. Методи доступу до полів класу можуть виконувати додаткову роботу при перевстановлення значення поля класу. Так, при установці властивості Ttable.Active в true проводиться читання даних з таблиці бази даних на жорсткому диску, кілька разів змінюється стан об'єкта, надсилаються події пов'язаним об'єктах, викликаються обробники делегованих подій, які пише програміст, і тільки потім встановлювати заново значення властивості в true. Для присвоєння властивості значення за замовчуванням, використовується ключове слово default, наприклад:

property Visible: boolean read Avisible write SetVisible default true; Це означає, що при запуску програми, компілятор встановити цю властивість в true. Однак я настійно рекомендую Вам встановлювати значення властивостей за умовчанням в конструкторі класса.Ви можете мати індексовані властивості. Ось приклад реалізації такого класу.

Type

TmyClass = class (TObject);

private

AmyList: Tlist; / / Контейнер покажчиків

Protected

Function GetMyList (Index: Integer): Pointer; / / Функція доступу по читанню

Procedure SetMyList (Index: Integer; Val: Pointer); / / Процедура доступу по запису

Public

...

Property MyList [Index: Integer]: Pointer read GetMyList write SetMyList; / / Оголошення індексованого властивості

End;

Тут ми бачимо, що властивість MyList індексованої - це елемент списку покажчиків. У квадратних дужках Вам потрібно вказати список індексів та їх типів. У загальному випадку, індексом може бути навіть рядок символів. Далі йде тип властивості і методи запису і читання. Функція читання повинна мати список формальних параметрів з усіма індексами властивості і повертати значення того ж типу, що і властивість. Процедура запису повинна мати список формальних параметрів з усіма індексами властивості і параметр для передачі встановлюваного значення того ж типу що і властивість. Велике значення має послідовність вказівки індексів і обов'язковість передачі значення властивості у процедурі запису останнім у списку формальних параметрів. Якщо индексированное властивість є основним і звернення саме до нього виробляється частіше за інших, то можна оголосити його як default, тоді не потрібно вказувати ім'я властивість для доступу до нього, наприклад:

Type

TmyClass = class (TObject);

private

AmyList: Tlist; / / Контейнер покажчиків

Protected

Function GetMyList (Index: Integer): Pointer; / / Функція доступу по читанню

Procedure SetMyList (Index: Integer; Val: Pointer); / / Процедура доступу по запису

Public

...

Property MyList [Index: Integer]: Pointer read GetMyList write SetMyList; default; / / Оголошення індексованого властивості за умовчанням

End;

...

Var

MyClass: TmyClass;

Begin

MyClass: = TmyClass.Create;

...

MyClass [3]: = AnyObject; / / Аналогічно MyClass.MyList [3]]: = AnyObject;

...

End;

Значення інкапсуляції в об'єктно-орієнтованому програмуванні важко переоцінити. Чого вартий хоча б те, що в Delphi до 100% полів класів доступ організований через властивості.

Успадкування

Якщо Ви хочете змінити або доповнити поведінка вже існуючого класу, то немає ніякої необхідності переписувати клас заново. Вам варто скористатися спадкуванням. Ви повинні оголосити, що новий клас є нащадком вже існуючого і додати в новий клас властивості і методи, які Вам необхідні або перекрити існуючі методи і властивості:

Type

TmyFirstClass = class (TObject)

Private

Protected

Public

Constructor Create (Val: Integer); virtual;

end;

TmySecondClass = class (TMyFirstClass)

Private

AmyField: string; / / Додали нове поле

Protected

Procedure SetMyField (Val: string); / / Додали процедуру

Public

Constructor Create (Val: Integer); override; / / перевантажили конструктор

Property MyField: string read AmyField write SetMyField; / / Додали властивість

End;

Є кілька правил області видимості об'єкта, які допоможуть Вам розібратися зі способами доступу до об'єктів та успадкування об'єктів:

Поля, властивості та методи секції public не мають обмежень на видимість.

Поля, властивості та методи секції private, доступні тільки в методах класу і у функціях, оголошених у тому ж модулі, де і клас.

Поля, властивості та методи секції protected теж доступні тільки з методів класу і функцій, оголошених у модулі, але вони доступні в класах, які є нащадками, в тому числі і оголошених у інших модулях.

При описі нащадків, Ви можете змінювати область видимості методів і властивостей. Можна розширювати область видимості, але не звужувати. Тобто якщо є властивість в секції private, ви можете зробити його public, але не навпаки. Ось приклад розширення області видимості:

Type

TmyClass = class (TObject)

Private

AmyField: Integer;

protected

property MyField: Integer read AmyField;

...

End;

TmySunClass = class (TMyClass)

...

Public

Property MyField; / / Тільки згадали його в іншій секції і він поміняв область видимості.

End;

Успадковані поля і методи нащадка є і в пращура. При збігу імен предка і нащадка говорять про перекриття нащадком полів або методів предка. За способом виклику методи класу можна розділити на статичні, віртуальні, динамічні, перевантажуються і абстрактні. Абстрактні методи не мають реалізації і повинні бути обов'язково перекрито в нащадках. Абстрактними можуть бути тільки віртуальні та динамічні методи, наприклад:

Type

TmyClass = class (TObject)

...

protected

procedure MyMethod (Val: Integer); virtual; abstract;

...

End;

Статичні методи і поля в об'єктах-нащадках перекриваються однаково: Ви можете без обмежень перекривати старі імена і при цьому змінювати тип методів, тому що при перекритті буде створено нове поле або метод з тим же ім'ям. Перекритий метод предка доступний в нащадку через зарезервоване слово inherited.Type

TmyClass = class (TObject)

...

protected

procedure MyMethod;

...

End;

TmySunClass = class (TmyClass)

...

protected

procedure MyMethod (Val: Integer);

...

End;

... Procedure TmySunClass.MyMethod (Val: Integer); begin

inherited MyMethod; / / Метод предка без параметрів, а метод нащадка вже з параметром, тобто ми поміняли тип процедури.

...

end;

За замовчуванням всі методи - статичні, тому їх адреси відомі вже на стадії компіляції, вони будуть викликатися при виконанні програми найшвидшим способом. Віртуальні і динамічні методи описуються за допомогою спеціальних директив virtual або dynamic. Ці методи можуть бути перекриті в нащадку однойменними методами, мають той самий тип.

І нарешті, хочу сказати, що Delphi не підтримує множинного успадкування. Для досягнення того ж самого, Вам доведеться використовувати просте успадкування від одного з потрібних класів, а другий клас додати як поле, і організувати доступ до властивостей другого класу через властивості спадкоємця. Так можна з'єднати кілька класів. Інший спосіб - це створити клас-контейнер, в якому в якості полів будуть потрібні включені класи, а інтерфейс доступу до них організувати через властивості контейнера.

Поліморфізм

Вказівником на екземпляр об'єктного типу може бути присвоєно адресу будь-якого примірника будь-якого з дочірніх типів. При зверненні до властивостей і методів через цей покажчик буде доступний саме примірник, адреса якого був присвоєний, а не предок. Це і є поліморфізм. Тобто Ви можете мати доступ до нащадка через покажчик об'єктного типу предка. Розглянемо приклад:

Type

TMyClass = class (TObject)

...

public

procedure GetData: string; virtual; abstract;

...

end;

TmySun1Class = class (TMyClass)

Protected

AmyField: string;

...

public

procedure GetData: string; override;

...

end;

TmySun2Class = class (TMyClass)

Protected

AmyField: Integer;

...

public

procedure GetData: string; override;

...

end;

...

implementation

procedure TmySun1Class.GetData: string;

begin

Result: = AmyField;

end;

procedure TmySun2Class.GetData: string;

begin

Result: = IntToStr (AmyField);

end;

...

Var

MyClass: TmyClass;

Class1: TmySun1Class;

Class2: TmySun2Class;

Begin

Class1: = TmySun1Class.Create;

Class2: = TmySun2Class.Create;

...

MyClass: = Class1;

Label1.Caption: = MyClass.GetData;

MyClass: = Class2;

Label2.Caption: = MyClass.GetData;

end;

Якщо подивитися на цей код уважно, то можна зрозуміти, що у компілятора немає можливості визначити метод якого саме класу потрібно викликати. Тому, для визначення адреси методу використовуються спеціальні таблиці, де зберігаються адреси на віртуальні та динамічні методи: VMT - таблиця віртуальних методів і DMT - таблиця динамічних методів. Коли компілятор зустрічає покажчик на віртуальний метод, то він шукає його адресу в VMT, де зберігаються всі адреси віртуальних методів класу успадкованих і перекритих, тому така таблиця займає багато пам'яті, хоча і спосіб виклику методу працює порівняно швидко. Динамічні методи викликаються повільніше, але займають менше пам'яті, тому що в них зберігаються адреси динамічних методів тільки даного класу та їх індекси. При виклику динамічних методів проводиться пошук по цій таблиці, якщо метод не знайдено, то пошук продовжується в DMT предків аж до Tobject, де викликається стандартний обробник виклику динамічних методів. Навіщо ж нам все це треба? При проектуванні ієрархії класів предметної області, потрібно статичними робити методи, які не змінюють своєї поведінки в нащадках, тобто при більш детальному розгляді явища. Динамічні і віртуальні методи можуть змінюватися при переході від загального до приватного. Згадайте клас Tfield, який є спільним предком для всіх класів-полів таблиці. Нащадки цього класу реалізують доступ до стовпчиків таблиці різних типів від цілого числа до BLOB масиву, проте, Ви можете мати зручний доступ до цих нащадкам через вказівник типу Tfield і працювати з ними однаково.

Перевантаження методів, процедур і функцій

Перевантаження оголошується за допомогою зарезервованого слова overload. Розглянемо приклад:

Type

TmyDateClass = class (TObject)

private

Adate: TdateTime;

...

Public

Procedure SetDate (Val: TDateTime); overload; / / Оголошуємо можливість перевантаження

...

end;

TmySecondDateClass = class (TmyDateClass)

private

Adate: TdateTime;

...

Public

Procedure SetDate (Val: string); overload; / / Оголошуємо можливість перевантаження

...

End; ...

implementationProcedure TmyDateClass. SetDate (Val: TDateTime);

Begin

Adate: = Val;

End;

Procedure TmySecondDateClass.SetDate (Val: string);

Begin

Adate: = StrToDate (Val);

End;

Під час роботи програми, ви можете використовувати у другому класі обидва методи SetDate. Якщо Ви передасте в якості параметра рядок, то буде викликаний метод другого класу, а якщо TdateTime, то метод предка. Можна перевантажувати і віртуальні методи, тільки замість override потрібно використовувати reintroduce, наприклад:

Type

TmyDateClass = class (TObject)

private

Adate: TdateTime;

...

Public

Procedure SetDate (Val: TDateTime); overload; virtual; / / Оголошуємо можливість перевантаження

...

end;

TmySecondDateClass = class (TmyDateClass)

private

Adate: TdateTime;

...

Public

Procedure SetDate (Val: string); reintroduce; overload; / / Оголошуємо можливість перевантаження

...

End; Ви можете використовувати перевантаження та для процедур і функцій не обов'язково при спадкуванні, і навіть процедур і функцій не класового типу, наприклад:

Function Myfunction (Val: string): string; overload;

Begin

Result: = Val + 'Ok!'

End;

Function Myfunction (Val: Extended): extended; overload;

Begin

Result: = Val / 2;

End;

Або

TmyDateClass = class (Tobject)

private

Adate: TdateTime;

...

Public

Procedure SetDate (Val: TDateTime); overload; / / Оголошуємо можливість перевантаження

Procedure SetDate (Val: string); overload; / / Оголошуємо можливість перевантаження

...

End;

Параметри за замовчуванням

Якщо Вам потрібно створити функцію, яка в якості параметрів майже завжди отримує одне і те ж значення, але все-таки іноді воно може змінюватися, то Вам потрібно оголосити параметри за умовчанням як формальних параметрів, наприклад:

Procedure MyProcedure (Val1: Extended; Val2: Integer = 2);

Begin

...

End;

Тоді Ви зможете викликати її такими способами:

MyProcedure (42.33); / / аналогічно MyProcedure (42.33, 2);

MyProcedure (15.6, 8);

Правило говорить, що всі параметри за замовчуванням повинні бути зосереджені в кінці списку формальних параметрів процедури або функції. Опускати параметри за замовчуванням, можна лише починаючи з кінця списку, тому потрібно впорядковувати параметри за замовчуванням за ступенем важливості.

Делегування

Подія - це властивості процедурного типу, призначені для створення користувальницької реакції на зовнішні впливи. Події в Delphi реалізується за рахунок створення поля процедурного типу та оголошення відповідного властивості класу, наприклад:

Type

TmyEvent = procedure (Sender: Tcomponent); of object; / / визначення процедурного типу

TmyClass = class (Tcomponent)

Private

FmyEvent: TmyEvent;

Protected

Procedure DoMyEvent;

published

property OnMyEvent: TmyEvent read FmyEvent write FmyEvent;

end;

Припустимо, Ви визначили функцію function MyProcedure (Sender: Tcomponent) для обробки події за допомогою інспектора об'єктів або написали вручну і нальоту привласнили об'єкту: MyClass.OnMyEvent: = MyProcedure. При настанні певних умов Ваш клас може викликати процедуру DoMyEvent, де буде викликана Ваша процедура MyProcedure так:

Procedure TmyClass. DoMyEvent;

Begin

If Assigned (FmyEvent) then FmyEvent (Self);

End; Ми бачимо, що був перевірений покажчик на користувальницьку процедуру обробки події, і якщо він дійсний, то викликається користувальницька процедура - це і є делегування. Зверніть увагу, що я розмістив властивість OnMyEvent в секції published для того, щоб програміст міг скористатися інспектором об'єктів для написання процедури обробки події.

Список літератури

Банніков. Н.А. Об'єктно-орієнтований підхід до програмування.

Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Реферат
43.1кб. | скачати


Схожі роботи:
Інтерактивна об`єктно-орієнтований підхід до побудови систем управління
Програмування мовою С з використанням об єктно орієнтованого програмування
Суб єктно-діяльнісний підхід до навчання
Об єктно-орієнтоване програмування
Особистісно орієнтований підхід у навчанні іноземної мови
Особистісно-орієнтований підхід у навчанні іноземної мови
Особистісно-орієнтований підхід у навчанні іноземної мови 2
Об`єктно орієнтоване програмування на Borland C
Об`єктно-орієнтоване програмування на Borland C
© Усі права захищені
написати до нас